查看原文
其他

你要的只是模块化,而不是微服务

张向前 分布式实验室 2023-01-28

架构有时候挺难捉摸的——人们不断提出新想法,这些想法在没有任何背景或者细微差别的情况下快速成为主流“实现方式”;而迫于寻求架构改进之法的行业则盲目跟进,不假思索地全盘接受。

在这股大有问题的风潮中,微服务正是最新一代弄潮儿。可微服务到底哪里好,我们又为什么要使用微服务?是时候对这个问题追根溯源了。


 1 
究其根本,微服务号称能带来……


很多好东西!

  • 可扩展性:能把代码拆分成更小的部分,各自独立开发、测试、部署和更新。

  • 专注:让开发者专注于解决业务问题和业务逻辑。

  • 可用性:后端数据必须始终可用于各种设备。

  • 简单性:简化大型企业级应用程序的开发。

  • 响应能力:让分布式应用程序得以扩展,从而响应不断变化的事务负载。

  • 可靠性:复制的服务器分组可在发生故障时继续保持运行,从而消除单点故障。即使发生故障,运行中的应用程序也可恢复至良好状态。

这些听着都挺耳熟,但这六条说法其实各有来历:两条来自微服务文献  (博文、论文等),两条来自20年前的EJB文献,两条来自Oracle Tuxedo——一项四十多年前的技术。各位朋友分得清它们的真实归属吗?

换句话说,咱们这个行业其实一直在用同样的炒作方法,同样的宣传伎俩。

不记得过去的人,注定要重蹈覆辙。

——George Santanyana,《理性的生活》(1905年)[1]

说起微服务这边的炒作,一篇企业博文[2]列出了选择微服务的十大理由:

  1. 能普及大数据最佳实践。微服务天然适用于数据管道架构,符合大数据的收集、摄取、处理和交付方式。数据管道中的各个步骤都会以微服务的形式处理各项小任务。

  2. 相对易于构建和维护。微服务的用途单一,这种设计意味着其能以较小的团队构建和维护。各个团队可以跨职能组建,同时也能专注于解决方案中的微服务子集。

  3. 支持更高代码质量。将整体解决方案模块化为离散组件,有助于应用开发团队每次专注于一小部分内容,从而简化整个编码和测试过程。

  4. 简化了跨团队协调流程。与以往涉及重量级进程间通信协议的传统面向服务架构(SOA)不同,微服务使用事件流技术降低了集成门槛。

  5. 支持实时处理。微服务架构的核心是发布/订阅框架,能够支持实时数据处理以提供即时输出与洞察。

  6. 有助于快速增长。微服务使得代码和数据能够重用模块化架构,轻松部署更多数据驱动的用例和解决方案,借此增加业务价值。

  7. 能带来更多产出。数据通常会以不同方式呈现给不同受众;微服务简化了为各最终用户提取数据的方式。

  8. 易于评估应用程序生命周期中的更新。高级分析环境,包括那些用于机器学习的分析环境,往往需要相应方法来根据新建模型评估原有计算模型。微服务架构中的A/B和多变量测试使用户能够轻松验证自己更新后的模型。

  9. 可实现规模伸缩。可伸缩性不仅有助于提供更多容量,也能轻松凸显出扩展瓶颈,之后在各微服务层级上解决这些瓶颈。

  10. 拥有大量流行工具。大数据世界中的各种技术(包括开源社区成果)在微服务架构中均运行良好。Apache Hadoop、Apache Spark、NoSQL数据库和各类流分析工具皆可用于微服务。

宣传材料看得够多了,下面我们一一对这些说法进行甄别,只是这回要从真实技术的角度出发:

  1. 能普及大数据最佳实践。自70年代以来,管道和过滤器架构就一直是软件工程中的组成部分。当时Unixes提出了几条原则[3]:

    让每个程序做好一件事。如果需要完成一项新工作,别通过添加新“功能”让旧程序复杂化,而应重新构建新程序。
    应将每个程序的输出看作另一个未知程序的输入。不要用无关信息混淆输出。严格避免使用列式或二进制输入格式,也不要坚持使用交互式输入。
  2. 相对易于构建和维护。参见以上Unix原则。

  3. 能支持更高质量的代码。如果一次专注于一小部分有助于提高质量,那请参见以上Unix原则。

  4. 简化了跨团队协调流程。这一点非常有趣,里面提到“面向服务架构”(SOA)……往往涉及重量级进程间通信协议——比如说JSON over HTTP?或者说,这是指一切SOA都需要SOAP、WSDL、XML Schema和WS-*完整规范集?讽刺的是,微服务并没有以任何方式阻止使用这些“重量级”协议,某些微服务甚至建议使用gRPC——一种跟IIOP非常相似的二进制协议,它来自CORBA,属于SOAP、WSDL、XML Schema和WS-*完整规范集等“重量级”协议的前身。

  5. 支持实时处理。实时处理其实并不是什么新鲜事物,多数此类系统确实需要用发布/订阅或者“事件总线”模型来实现,但跟微服务架构没什么必要联系。

  6. 有助于快速增长。“重用模块化架构”——好吧,大家还记得有多少事物都在以“重用”为卖点吗?编程语言当然是其一(OOP、函数式语言、过程语言),还有库、框架等等……也许有一天,大家会在忍无可忍下表示“去他的重用,我们不在乎”。

  7. 能带来更多产出。“数据集通常会以不同方式呈现给不同受众”,这说的不是SAP的Crystal Reports吗?

  8. 易于评估应用程序生命周期中的更新。机器学习和高级分析环境需要“根据新建模型评估现有计算模型”……听着挺热门的,但其实并没表达出什么实质内容。

  9. 可实现规模伸缩。这就有点好笑了,EJB、事务性中间件处理(比如Tuxedo)和大型机,谁不能实现规模伸缩?

  10. 拥有大量流行工具。我得说一句,行业中的每次重大炒作都少不了工具的捧场,特别是在实在没什么可宣传的时候。可能大部分读者朋友没经历过CASE的时代,但至少对UML有所耳闻吧?

而且聪明的朋友可能已经注意到,上面大概一半的观点都在强调共通的主题——创建和维护更小、更独立的代码和数据“块”,让各块间相互版本化,使用共同的输入和输出以实现整体系统集成。这就好像……


 2 
究其根本,我们在微服务中发现的是……


模块。

没错,大量的低级“模块”。这个诞生自1970年代的概念,一直是大部分编程语言的核心。也可能更早,只是很多旧语言并没有把相同的设计理念概括成“模块”这个表达。

比如在CLR(C#、F#、Visual Basic等)中,它被称为“程序集”;在JVM(Java、Kotlin、Clojure、Scala、Groovy等)上,则被称为“JAR”或“包”;在我们熟悉的操作系统动态链接库上也有它的身影(比如Windows上的DLL,Unix上的so或a,还有MacOS那隐藏在/Library目录下的Frameworks)。

无论如何,它们都服务于相同的目标:创造一个独立构建、管理、版本控制和部署的代码单元,以供重复使用。

考虑到模块的这一实际定义,我们引用一篇计算机科学基础论文中的表述:

项目工作的明确定义与细分,保障了系统的模块化。每个任务都将形成一个独立的、不同于其他的程序模块。在实现时,每个模块及其输入/输出都经过明确定义,与其他系统的预期接口不会混淆。在检查开始前,先对各个任务进行同步以避免调度问题。最终,系统会以模块化形式进行维护;系统错误和缺陷可被追溯至特定的系统模块,从而限制错误搜索的具体范围。

以上内容来自David Parnas撰写的开创性论文《On the Criteria To Be Used in Decomposing Systems into Modules[4]》,文章是在50多年前的1971年写成的。这里提出的明确定义“独立的、不同于其他的程序模块”已经涵盖了微服务的约半数优势,就是说同样的方法我们已经用了50年之久。

所以,何必要为微服务而激动不已?

毕竟微服务的实质跟微服务、服务甚至是分布式系统,根本就没有关系。


 3 
微服务的实质,在于……


组织清晰度。

亚马逊是最早公开讨论微服务概念的企业之一,但他们自己并没有特别积极地运用这种新架构。毕竟为什么要瞎折腾呢,DBA团队得做相应的架构变更、QA需要开发新测试来发现bug、基础设施团队得采购新一批服务器、UX团队则要为演示创建原型。这些都是额外的负担,也代表着现实阻力。

在这种情况下,开发团队根本就没法顺利推进自己预想的架构调整。而以上提到的各个部门,只是普通IT团队中各类职能(分析、开发、设计、测试、数据管理、部署、管控等)的小缩影。

要想实际推动,则要么保证现有团队能找到不同的技术组合,要么硬性要求每位团队成员都具备完整的技能集(即所谓「全栈开发者」),可这无疑会大大增加招聘难度。另外,团队还需要对自己的生产中断事件负责,每位成员都得随叫随到——这些都会体现在薪酬待遇和法律影响上。当然,如果真能打通这些关节要害,那各个团队确实能够独立构建自己的工件,于是生产力水平将一路飙升、再无阻碍。

可这种只有理论可行性的事情,真能实现吗?


 4 
在微服务当中,我们还发现了……


分布式计算谬误。

有些朋友可能不太熟悉,这里的谬误来自Peter Deutsch在80年代给Sun的同事们做的演讲。1994年,Ann Wolrath和Jim Waldo的开创性论文《A Note on Distributed Computing[5]》则再次提及。

“无论如何定义「正确」,分布式系统都很难在性能、可靠性和可扩展性方面真正正确。”(松散解释)

当我们将系统拆分成运行在单一操作系统节点上的内存模块时,即使是在五十年前,跨进程或库边界进行数据传递的成本也可以忽略不计。但当我们开始跨网络线路传递数据时(也是目前大多数微服务的通行作法),则通信的延迟会瞬间提升五到七个数量级。这不仅仅要求我们向网络添加更多节点来“扩展”,同时也令整个系统快速恶化。

没错,通过将微服务托管在同一台机器上、加载到运行各独立微服务容器化镜像的虚拟机集群内,确实能降低不同服务间的相互影响。(例如使用Docker Compose或Kubernetes托管一组Docker容器。)但这种方式会增加虚拟机进程边界间的延迟(因为我们必须遵循七层模型规则,即使其中某些层可以完全模拟,产生的延迟仍无法忽视),而且会在各独立节点上引发运行可靠性问题。

更糟糕的是,还没等我们切实解决分布式计算谬误,那边又冒出了与之相关但又独立存在的新麻烦:企业计算谬误[6]。


 5 
探究微服务的核心,我们最好能……


重新思考自己的需求。

真有必要把问题拆分成一个个独立实体吗?用托管在Docker容器里的独立进程就能做到这一点,或者也可以在遵循标准化API约定或者其他选项的应用程序服务器中引入独立模块。

这是个有成熟解的问题,过去20年来已经有无数种技术能够实现,包括servlet、ASP.NET、Ruby、Python、C++,甚至是闻者头痛的Perl。其中的关键,是如何建立起易于理解的集成和通信约定的通用架构基础。

我们要不要减少开发团队面对的依赖项?认真观察这些依赖项,再跟合作伙伴共同确定可以把其中哪些做做整合。如果组织不想打破结构体系中“以技能为中心”的指导思路(就是把员工明确划分成「数据库组」、「基础设施组」、「QA组」和「开发组」等),那也至少要尝试建立一个边界相对模糊的报告结构,把每位成员视为团队“矩阵”里的一个元素。

而且最重要的是,一定要保证团队对自己正在构建的内容拥有清晰认知,保证他们能轻松向他人解释自己服务/微服务/模块的核心意义。关键是给团队以方向和目标、达成目标的自主权,同时辅以必要的鼓励和引导。

这就是事情的真相。你想要微服务?不,你想要的只是模块。

相关链接:

  1. https://americanart.si.edu/artwork/those-who-cannot-remember-past-are-condemned-repeat-it-george-santayana-life-reason-1905

  2. https://www.integrella.com/expertise/microservices/

  3. https://en.wikipedia.org/wiki/Unix_philosophy

  4. https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf

  5. https://scholar.harvard.edu/files/waldo/files/waldo-94.pdf

  6. http://blogs.newardassociates.com/blog/2016/enterprise-computing-fallacies.html


推荐阅读:


分布式实验室策划的《Kubernetes实战集训营》正式上线了。这门课程通过5天线上培训,4个课后大作业,40天课后辅导,把Kubernetes的60多个重要知识点讲给你,并通过实战让你掌握Kubernetes。培训重实战、重项目、更贴近工作,边学边练,2月25日正式开课。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存